package com.javatest.springboot3minio.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import com.javatest.springboot3minio.exception.FileNameNullException;
import com.javatest.springboot3minio.exception.InvalidExpiresRangeException;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Item;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;

/**
 * Created with IntelliJ IDEA. Description:
 *
 * @author lzy
 * @since 2022/10/13 下午4:14
 */
public class MinioFileUtil {
  private static final Logger LOGGER = LoggerFactory.getLogger(MinioFileUtil.class);
  private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
  private static final String UPLOAD_FILE_ERROR = "[Minio] Upload file meet error.";
  private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 3600;
  private static MinioClient MINIO_CLIENT;

  @Autowired
  public void setMinioClient(MinioClient minioClient) {
    MINIO_CLIENT = minioClient;
  }

  public static MinioFileUtil getFileUtilSingleton() {
    return MinioFileUtilHolder.INSTANCE;
  }

  /**
   * List files in bucket list.
   *
   * @return the list
   */
  public static List<Item> listFilesInBucket(String bucketName) {
    final Iterable<Result<Item>> results =
        MINIO_CLIENT.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
    List<Item> items = new ArrayList<>();
    results.forEach(
        result -> {
          try {
            items.add(result.get());
          } catch (Exception e) {
            LOGGER.error("[Minio] Get file info meet error.", e);
          }
        });

    return items;
  }

  /**
   * List file names in bucket list.
   *
   * @return the list
   */
  public static List<String> listFileNamesInBucket(String bucketName) {
    return listFilesInBucket(bucketName).stream()
        .map(Item::objectName)
        .collect(Collectors.toList());
  }

  /**
   * Stat object stat object response.
   *
   * @param bucketName the bucket name
   * @param objectName the object name
   * @return the stat object response
   */
  public static Optional<StatObjectResponse> statObject(String bucketName, String objectName) {
    try {
      return Optional.of(
          MINIO_CLIENT.statObject(
              StatObjectArgs.builder().bucket(bucketName).object(objectName).build()));
    } catch (Exception e) {
      LOGGER.error("[Minio] Get object meta info meet error.", e);
    }

    return Optional.empty();
  }

  /**
   * Gets file preview url.
   *
   * @param fileName the file name
   * @return the file preview url
   */
  public static Optional<String> getFilePreviewUrl(String bucketName, String fileName) {
    return presignedGetObject(bucketName, fileName, DEFAULT_EXPIRY_TIME);
  }

  /**
   * 生成一个给HTTP GET请求用的presigned URL 浏览器/移动端的客户端可以用这个URL进行下载， 即使其所在的存储桶是私有的。 这个presigned
   * URL可以设置一个失效时间，默认值是7天
   *
   * @param bucketName the bucket name 存储桶名称
   * @param objectName the object name 存储桶里的对象
   * @param expires the expires 失效时间(单位是秒)，默认是7天，不得大于7天
   * @return the string
   */
  public static Optional<String> presignedGetObject(
      String bucketName, String objectName, int expires) {
    final boolean exists = MinioBucketUtil.bucketExists(bucketName);
    if (!exists) {
      return Optional.empty();
    }

    if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) {
      throw new InvalidExpiresRangeException(
          "Expires " + expires + " is out of range 1 to " + DEFAULT_EXPIRY_TIME);
    }

    String url = null;
    try {
      url =
          MINIO_CLIENT.getPresignedObjectUrl(
              GetPresignedObjectUrlArgs.builder()
                  .bucket(bucketName)
                  .object(objectName)
                  .expiry(expires)
                  .method(Method.GET)
                  .build());
    } catch (Exception e) {
      LOGGER.error("[Minio] Get presigned get url meet error.", e);
    }

    return Optional.of(url);
  }

  /**
   * 生成一个给HTTP PUT请求用的presigned URL 浏览器/移动端的客户端可以用这个URL进行上传，即使其所在的存储桶是私有的。 这个presigned
   * URL可以设置一个失效时间，默认值是7天
   *
   * @param bucketName the bucket name
   * @param objectName the object name
   * @param expires the expires
   * @return the string
   */
  public static Optional<String> presignedPostObject(
      String bucketName, String objectName, int expires) {
    final boolean exists = MinioBucketUtil.bucketExists(bucketName);
    if (!exists) {
      return Optional.empty();
    }

    if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) {
      throw new InvalidExpiresRangeException(
          "Expires " + expires + " is out of range 1 to " + DEFAULT_EXPIRY_TIME);
    }

    String url = null;
    try {
      url =
          MINIO_CLIENT.getPresignedObjectUrl(
              GetPresignedObjectUrlArgs.builder()
                  .bucket(bucketName)
                  .object(objectName)
                  .expiry(expires)
                  .method(Method.POST)
                  .build());
    } catch (Exception e) {
      LOGGER.error("[Minio] Get presigned put url meet error.", e);
    }

    return Optional.of(url);
  }

  /**
   * 上传文件
   *
   * @param bucketName the bucket name
   * @param objectName the object name
   * @param is the is
   * @param size the size
   * @param contentType the content type
   * @return the boolean
   */
  public static boolean uploadFile(
      String bucketName, String objectName, InputStream is, long size, String contentType) {
    final boolean flag = MinioBucketUtil.bucketExists(bucketName);
    if (flag) {
      try {
        final PutObjectArgs args =
            PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(
                    is, size < 1 ? is.available() : size, -1)
                .contentType(StringUtils.isBlank(contentType) ? DEFAULT_CONTENT_TYPE : contentType)
                .build();
        MINIO_CLIENT.putObject(args);
      } catch (Exception e) {
        LOGGER.error(UPLOAD_FILE_ERROR, e);
      }
    } else {
      LOGGER.warn("[Minio] bucket [{}] is not exist, can't upload file.", bucketName);
    }

    return false;
  }

  /**
   * 上传文件
   *
   * @param multipartFile the multipart file
   * @return the string
   */
  public static String uploadFile(String bucketName, MultipartFile multipartFile) {
    final String originalFilename = multipartFile.getOriginalFilename();
    if (StringUtils.isBlank(originalFilename)) {
      throw new FileNameNullException();
    }

    boolean flag = false;
    try {
      flag =
          uploadFile(
              bucketName,
              originalFilename,
              multipartFile.getInputStream(),
              multipartFile.getSize(),
              multipartFile.getContentType());
    } catch (Exception e) {
      LOGGER.error(UPLOAD_FILE_ERROR, e);
    }

    return flag ? StringUtils.EMPTY : originalFilename;
  }

  /**
   * 上传文件
   *
   * @param file the file
   * @return the string
   */
  public static String uploadFile(String bucketName, File file) {
    final String fileName = FileNameUtil.getName(file);
    if (StringUtils.isBlank(fileName)) {
      throw new FileNameNullException();
    }
    boolean flag = false;
    try {
      flag = uploadFile(bucketName, fileName, FileUtil.getInputStream(file), 0, null);
    } catch (Exception e) {
      LOGGER.error(UPLOAD_FILE_ERROR, e);
    }

    return flag ? StringUtils.EMPTY : fileName;
  }

  /**
   * Download file.
   *
   * @param fileName the file name
   * @param response the response
   */
  public static void downloadFile(
      String bucketName, String fileName, HttpServletResponse response) {
    final GetObjectArgs args = GetObjectArgs.builder().bucket(bucketName).object(fileName).build();
    try (final GetObjectResponse res = MINIO_CLIENT.getObject(args)) {
      try (ServletOutputStream stream = response.getOutputStream()) {
        IoUtil.copy(res, stream);
        response.setCharacterEncoding("utf-8");
        response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
        stream.flush();
      }
    } catch (Exception e) {
      LOGGER.error("[Minio] Download file meet error.", e);
    }
  }

  /**
   * Remove boolean.
   *
   * @param fileName the file name
   * @return the boolean
   */
  public static boolean remove(String bucketName, String fileName) {
    try {
      MINIO_CLIENT.removeObject(
          RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
    } catch (Exception e) {
      LOGGER.error("Remove file meet error.", e);
      return false;
    }

    return true;
  }

  private static class MinioFileUtilHolder {
    private static final MinioFileUtil INSTANCE = new MinioFileUtil();
  }
}
